package org.codingeasy.hotbean;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * 热bean上下文
 *
 * @author : KangNing Hu
 * @since 2.0
 */
@Slf4j
@Component
public class HotBeanContext implements ApplicationContextAware {

  private ApplicationContext applicationContext;

  private Map<Class, BeanHolder> hotBeanMap = new HashMap<>();

  private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

  @Autowired
  private HotClassLoaderManager hotClassLoaderManager;

  /**
   * 获取bean
   *
   * @param name bean名称
   * @param <T> bean 类型
   * @return 先重spring 容器中获取如果获取不到在冲 当前容器获取
   */
  public <T> T getBean(String name, Class<T> tClass) {
    /**
     * 从spring 容器中获取
     */
    T bean = getBeanFromSpring(name, tClass);
    if (bean != null) {
      return bean;
    }
    /**
     * 读取当前容器
     */
    ReadLock readLock = readWriteLock.readLock();
    readLock.lock();
    try {
      for (Map.Entry<Class, BeanHolder> entry : hotBeanMap.entrySet()) {
        if (tClass.isAssignableFrom(entry.getKey()) && entry.getValue().getName().equals(name)) {
          return (T)  entry.getValue().getBean();
        }
      }
    } finally {
      readLock.unlock();
    }
    throw new RuntimeException(String.format("没有找到指定bean [%s,%s]", name, tClass));
  }

  /**
   * 获取bean 列表
   *
   * @param tClass bean 类型
   * @param <T> 范型参数
   * @return 从spring 容器中以及当前容器中获取并合并
   */
  public <T> List<T> getBeans(Class<T> tClass) {
    List<T> beans = new ArrayList<>();
    /**
     * 从spring 容器中获取
     */
    List<T> springBeans = getBeansFromSpring(tClass);
    beans.addAll(springBeans);
    /**
     * 从当前容器中获取
     */
    ReadLock readLock = readWriteLock.readLock();
    readLock.lock();
    try {
      for (Map.Entry<Class, BeanHolder> entry : hotBeanMap.entrySet()) {
        if (tClass.isAssignableFrom(entry.getKey())) {
          beans.add((T) entry.getValue().getBean());
        }
      }
    } finally {
      readLock.unlock();
    }
    return beans;
  }


  /**
   * 注册热bean
   *
   * @param className class名称
   * @param codeContext 代码内容
   */
  public void register(String className, String codeContext) {
    log.info("注册 className {} \n {}" , className , codeContext);
    /**
     * 加载class
     */
    Class<?> oldClass = loaderOldClass(className);
    Class<?> aClass = loaderClass(className, codeContext);
    //获取bean名称
    String beanName = getBeanName(aClass);
    //创建bean
    Object bean = createBean(aClass);
    //获取写锁
    WriteLock writeLock = readWriteLock.writeLock();
    writeLock.lock();
    try {
      if (oldClass != null && hotBeanMap.containsKey(oldClass)) {
        log.info("移除hot bean {} ", beanName);
        hotBeanMap.remove(oldClass);
      }
      hotBeanMap.put(aClass , new BeanHolder(beanName , bean));
    } catch (Exception e) {
      throw new RuntimeException(e);
    } finally {
      writeLock.unlock();
    }
  }

  /**
   * 加载旧的class
   * @param className class name
   * @return
   */
  private Class<?> loaderOldClass(String className) {
    ClassLoader classLoader = hotClassLoaderManager.getClassLoader(className);
    if (classLoader == null){
      return null;
    }
    try {
      return classLoader.loadClass(className);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e.getMessage()  ,e);
    }
  }

  /**
   * 注销 class
   */
  public void remove(String className) {
    WriteLock writeLock = readWriteLock.writeLock();
    writeLock.lock();
    try {
      Class<?> aClass = hotClassLoaderManager.loadClass(className);
      hotBeanMap.remove(aClass);
    } catch (Exception e) {
      throw new RuntimeException(e.getMessage(), e);
    } finally {
      writeLock.unlock();
    }
    hotClassLoaderManager.remove(className);
  }


  /**
   * 创建bean
   *
   * @param aClass class 对象
   */
  private Object createBean(Class aClass) {
    log.info("创建class {} 的 bean" , aClass);
    /**
     * 创建bean 并自动注入
     */
    AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext
        .getAutowireCapableBeanFactory();
    Object bean = BeanUtils.instantiateClass(aClass);
    autowireCapableBeanFactory.autowireBean(bean);
    return bean;
  }

  /**
   * 加载class
   *
   * @param className 类名
   * @param codeContext 代码内容
   */
  private Class<?> loaderClass(String className, String codeContext) {
    return JavaDynamicCompile.compileAndLoader(codeContext, className);
  }

  /**
   * 获取bean名称
   *
   * @param tClass class 对象
   */
  private String getBeanName(Class tClass) {
    Component component = (Component) tClass.getAnnotation(Component.class);
    if (component != null && !StringUtils.isEmpty(component.value())) {
      return component.value();
    }
    return tClass.getName();
  }

  /**
   * 从spring 容器中获取bean列表
   *
   * @param tClass bean class类型
   */
  private <T> List<T> getBeansFromSpring(Class<T> tClass) {
    Map<String, T> map = applicationContext.getBeansOfType(tClass);
    if (!MapUtils.isEmpty(map)) {
      return new ArrayList<>(map.values());
    }
    return new ArrayList<>();
  }


  /**
   * 从spring 容器中获取bean
   *
   * @param name bean名称
   * @param tClass bean 类型
   * @param <T> 范型参数
   */
  private <T> T getBeanFromSpring(String name, Class<T> tClass) {
    String[] beanNames = applicationContext.getBeanNamesForType(tClass);
    if (!ArrayUtils.isEmpty(beanNames)) {
      for (String beanName : beanNames) {
        if (beanName.equals(name)) {
          return applicationContext.getBean(beanName, tClass);
        }
      }
    }
    return null;
  }


  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }
}
